#include "network.h"

static void* pthread_network_tcp_task(void *context)
{
    int32_t ret = 0;
    struct network * pnetwork = NULL;
    int retval;
    struct tcp_client* client;

    if(!context){
        printf("create tcp task error\n");
        return NULL;
    }

    client = (struct tcp_client*)context;
    pnetwork = (struct network *)client->ptr;

    DBG(DBG_INFO, "TCP client %d(online num:%d)\n", (int)client->id, pnetwork->client_nums);
    struct net_msg rx_msg = {0};

    while(pnetwork->running_flag){
        fd_set rfds;
        struct timeval tv;

        FD_ZERO(&rfds);
        FD_SET(client->socket, &rfds);

        tv.tv_sec = 5;
        tv.tv_usec = 0;

        retval = select(client->socket + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1){
            DBG(DBG_ERR, "select error\n");
            break;
        } else if (retval == 0){
            continue;
        }

        retval = read(client->socket, rx_msg.buffer, sizeof(rx_msg.buffer));
        if(retval > 0) {
            rx_msg.length = retval;
            DBG(DBG_INFO, "Receive %d byte(s) data\n", rx_msg.length);
            printf("data(hex):");
            for(int i = 0; i < rx_msg.length; i++)
                printf("%02X ", rx_msg.buffer[i]);
            printf("\n");
            
            pnetwork->op->write(pnetwork, client, rx_msg.buffer, rx_msg.length);
        }else if(retval == 0) {
            printf("client quit\n");
            break;
        } else {
            break;
        }
    }
    close(client->socket);
    ret = pnetwork->op->put_client(pnetwork, &pnetwork->free_client_queue, client);
    if(ret < 0){
        DBG(DBG_ERR, "cannot put client from queue\n");
        return NULL;
    }
    pnetwork->client_nums--;
    DBG(DBG_INFO, "TCP client %d exit(online num:%d)\n", (int)client->id, pnetwork->client_nums);

    return NULL;
}

static void* pthread_network_handle(void *context)
{
    struct network * pnetwork = NULL;

    pnetwork = (struct network*)context;
    if(!pnetwork){
        printf("create thread error\n");
        return NULL;
    }

    while(pnetwork->running_flag){
        fd_set rfds;
        struct timeval tv;
        int retval;
        socklen_t addrlen;

        FD_ZERO(&rfds);
        FD_SET(pnetwork->sock_fd, &rfds);

        tv.tv_sec = 5;
        tv.tv_usec = 0;

        retval = select(pnetwork->sock_fd + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1){
            DBG(DBG_ERR, "select error\n");
            break;
        } else if (retval == 0){
            continue;
        }
        addrlen = sizeof(struct sockaddr_in);
        memset(pnetwork->rx_buffer.buffer, 0, sizeof(pnetwork->rx_buffer.buffer));
        retval = recvfrom(pnetwork->sock_fd, pnetwork->rx_buffer.buffer, MAX_NET_MSG_LENGTH, 0, (struct sockaddr *) &pnetwork->s_addr, &addrlen);
        if(retval > 0){
            pnetwork->rx_buffer.length = retval;
            DBG(DBG_INFO, "Receive %d byte(s) data\n", pnetwork->rx_buffer.length);
            printf("data(hex):");
            for(int i = 0; i < pnetwork->rx_buffer.length; i++)
                printf("%02X ", pnetwork->rx_buffer.buffer[i]);
            printf("\n");

            pnetwork->op->write(pnetwork, NULL, pnetwork->rx_buffer.buffer, pnetwork->rx_buffer.length);
        }
    }
    close(pnetwork->sock_fd);

    return NULL;
}

static void* pthread_network_tcp_handle(void *context)
{
    int32_t ret = 0;
    struct network * pnetwork = NULL;
    struct tcp_client* client = NULL;

    pnetwork = (struct network*)context;
    if(!pnetwork){
        DBG(DBG_ERR, "create tcp receive thread error\n");
        return NULL;
    }

    while(pnetwork->running_flag){
#if 0
        if(pnetwork->client_nums >= MAX_TCP_CLIENT){
            DBG(DBG_WARN, "Have reached max clients, waitting for other client close\n");
            sleep(1);
            continue;
        }

        client = pnetwork->op->get_client(pnetwork, &pnetwork->free_client_queue);
        if(!client){
            DBG(DBG_ERR, "cannot get client from queue\n");
            sleep(1);
            continue;
        }
        socklen_t len = sizeof(client->s_in);

        client->socket = accept(pnetwork->sock_fd, (struct sockaddr*)&client->s_in, &len);
        if(client->socket < 0) {
            perror("accept");
            ret = pnetwork->op->put_client(pnetwork, &pnetwork->free_client_queue, client);
            if(ret < 0){
                DBG(DBG_ERR, "cannot put client from queue\n");
            }
            continue;//listen next
        }
        DBG(DBG_INFO, "get new client [%s: %d]\n", inet_ntoa(client->s_in.sin_addr), ntohs(client->s_in.sin_port));

        ret = pthread_create(&client->task_pid, NULL, pthread_network_tcp_task, client);
        if(ret < 0){
            DBG(DBG_ERR,"pthread_create error\n");
            ret = pnetwork->op->put_client(pnetwork, &pnetwork->free_client_queue, client);
            if(ret < 0){
                DBG(DBG_ERR, "cannot put client from queue\n");
            }
            continue;
        }
        pthread_detach(client->task_pid);
        pthread_join(client->task_pid, NULL);

        pnetwork->client_nums++;
#else
        fd_set rfds;
        struct timeval tv;
        int retval;
        socklen_t addrlen;

        FD_ZERO(&rfds);
        FD_SET(pnetwork->sock_fd, &rfds);

        tv.tv_sec = 5;
        tv.tv_usec = 0;

        retval = select(pnetwork->sock_fd + 1, &rfds, NULL, NULL, &tv);
        if (retval == -1){
            DBG(DBG_ERR, "select error\n");
            break;
        } else if (retval == 0){
            continue;
        }
        addrlen = sizeof(struct sockaddr_in);

        if(pnetwork->client_nums >= MAX_TCP_CLIENT){
            DBG(DBG_WARN, "Have reached max clients, waitting for other client close\n");
            continue;
        }

        client = pnetwork->op->get_client(pnetwork, &pnetwork->free_client_queue);
        if(!client){
            DBG(DBG_ERR, "cannot get client from queue\n");
            continue;
        }

        client->socket = accept(pnetwork->sock_fd, (struct sockaddr*)&client->s_in, &addrlen);
        if(client->socket < 0) {
            perror("accept");
            ret = pnetwork->op->put_client(pnetwork, &pnetwork->free_client_queue, client);
            if(ret < 0){
                DBG(DBG_ERR, "cannot put client from queue\n");
            }
            continue;//listen next
        }
        DBG(DBG_INFO, "get new client [%s: %d]\n", inet_ntoa(client->s_in.sin_addr), ntohs(client->s_in.sin_port));

        ret = pthread_create(&client->task_pid, NULL, pthread_network_tcp_task, client);
        if(ret < 0){
            DBG(DBG_ERR,"pthread_create error\n");
            ret = pnetwork->op->put_client(pnetwork, &pnetwork->free_client_queue, client);
            if(ret < 0){
                DBG(DBG_ERR, "cannot put client from queue\n");
            }
            continue;
        }
        pthread_detach(client->task_pid);
        pthread_join(client->task_pid, NULL);

        pnetwork->client_nums++;
#endif
    }
    close(pnetwork->sock_fd);
    DBG(DBG_INFO, "TCP server exit\n");

    return NULL;
}

static int32_t network_init(struct network* pnetwork)
{
    int32_t ret = 0;
    
    INIT_LIST_HEAD(&(pnetwork->head));
    lock_init(&(pnetwork->lock));
    lock_init(&(pnetwork->notify_lock));
    wait_init(&(pnetwork->wait));

    INIT_LIST_HEAD(&(pnetwork->free_client_queue));
    INIT_LIST_HEAD(&(pnetwork->used_client_queue));

    pnetwork->opened = 0;
    
    return ret;
}

static int32_t network_release(struct network* pnetwork)
{
    pthread_join(pnetwork->pid, NULL);

    lock_destroy((&pnetwork->notify_lock));
    lock_destroy((&pnetwork->lock));
    wait_destroy((&pnetwork->wait));

    return 0;
}

static int32_t network_udp_setup(struct network* pnetwork, uint16_t port)
{
	int32_t ret = 0;

    if(pnetwork->opened){
        DBG(DBG_ERR, "udp is opened\n");
        return -1;
    }

    pnetwork->type = NETWORK_TYPE_UDP;
	
	pnetwork->sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(pnetwork->sock_fd < 0){
        DBG(DBG_ERR, "can't get socket\n");
        return -1;
    }

    memset(&pnetwork->s_addr, 0, sizeof(pnetwork->s_addr));
    pnetwork->s_addr.sin_family = AF_INET;
    pnetwork->s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    pnetwork->s_addr.sin_port = htons(port);
    memset(pnetwork->s_addr.sin_zero, 0x00, 8);

    ret = bind(pnetwork->sock_fd, (struct sockaddr *)&pnetwork->s_addr, sizeof(pnetwork->s_addr));
    if (ret < 0) {
        DBG(DBG_ERR, "can't bind port\n");
        close(pnetwork->sock_fd);
        return ret;
    }

    pnetwork->running_flag = 1;
    ret = pthread_create(&pnetwork->pid, NULL, pthread_network_handle, pnetwork);
    if(ret < 0){
        DBG(DBG_ERR,"pthread_create error\n");
        close(pnetwork->sock_fd);
        return ret;
    }

    pnetwork->opened = 1;
	
	return ret;
}

static int32_t network_tcp_setup(struct network* pnetwork, uint16_t port)
{
	int32_t ret = 0;

    if(pnetwork->opened){
        DBG(DBG_ERR, "tcp is opened\n");
        return -1;
    }
    INIT_LIST_HEAD(&(pnetwork->free_client_queue));
    INIT_LIST_HEAD(&(pnetwork->used_client_queue));

    pnetwork->type = NETWORK_TYPE_TCP;
    pnetwork->client_nums = 0;
	
	pnetwork->sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if(pnetwork->sock_fd < 0){
        DBG(DBG_ERR, "can't get socket\n");
        return -1;
    }
    int flags = fcntl(pnetwork->sock_fd, F_GETFL, 0);
    fcntl(pnetwork->sock_fd, F_SETFL, flags | O_NONBLOCK);

    int optval = 1;
    ret = setsockopt(pnetwork->sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if(ret < 0){
        perror("setsockopt:");
        close(pnetwork->sock_fd);
        return ret;
    }

    memset(&pnetwork->s_addr, 0, sizeof(pnetwork->s_addr));
    pnetwork->s_addr.sin_family = AF_INET;
    pnetwork->s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    pnetwork->s_addr.sin_port = htons(port);

    ret = bind(pnetwork->sock_fd, (struct sockaddr *)&pnetwork->s_addr, sizeof(pnetwork->s_addr));
    if (ret < 0) {
        DBG(DBG_ERR, "can't bind port,errno:%d\n", ret);
        close(pnetwork->sock_fd);
        return ret;
    }

    ret = listen(pnetwork->sock_fd, 10);
    if (ret == -1) {
        DBG(DBG_ERR, "listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        close(pnetwork->sock_fd);
        return -1;
    }

    struct tcp_client* client = NULL;
    for(int i = 0; i < MAX_TCP_CLIENT; i++){
        client = &pnetwork->tcp_client[i];
        client->id = i;
        client->ptr = pnetwork;
        ret = pnetwork->op->put_client(pnetwork, &pnetwork->free_client_queue, client);
        if(ret < 0){
            DBG(DBG_ERR, "cannot put client from queue\n");
            close(pnetwork->sock_fd);
            return -1;
        }
    }

    pnetwork->running_flag = 1;
    ret = pthread_create(&pnetwork->pid, NULL, pthread_network_tcp_handle, pnetwork);
    if(ret < 0){
        DBG(DBG_ERR,"pthread_create error\n");
        close(pnetwork->sock_fd);
        return ret;
    }

    pnetwork->opened = 1;
	
	return ret;
}

static int32_t network_close(struct network* pnetwork)
{
    int32_t ret = 0;

    if(!pnetwork->opened){
        DBG(DBG_ERR, "tcp is closed\n");
        return -1;
    }

    pnetwork->running_flag = 0;
    pthread_join(pnetwork->pid, NULL);
    pnetwork->opened = 0;

    return ret;
}

static int32_t network_write(struct network* pnetwork, struct tcp_client* pclient, uint8_t *data, uint32_t len)
{
	int32_t ret = 0;

    if(pnetwork->type == NETWORK_TYPE_UDP) {
        ret = sendto(pnetwork->sock_fd, data, len, 0, (struct sockaddr *)&pnetwork->s_addr, sizeof(pnetwork->s_addr));
        if(ret <= 0){
            DBG(DBG_ERR, "udp send message error\n");
        }
    } else if(pnetwork->type == NETWORK_TYPE_TCP) {
        ret = write(pclient->socket, data, len);
        if(ret <= 0){
            DBG(DBG_ERR, "tcp send message error\n");
        }else if(ret < len){
            DBG(DBG_WARN, "tcp send %d byte(s) of %dbyte(s)\n", ret, len);
        }
    }

	return ret;
}

static int32_t network_put_client(struct network* pnetwork, struct list_head* queue, struct tcp_client* pclient)
{
    lock(&pnetwork->lock);
    list_add_tail(&pclient->head, queue);
    unlock(&pnetwork->lock);
    return 0;
}

static struct tcp_client* network_get_client(struct network* pnetwork, struct list_head* queue)
{
    struct tcp_client* pclient;
    pclient = NULL;
    lock(&pnetwork->lock);
    if(!list_empty(queue)){
        pclient = list_first_entry(queue, struct tcp_client, head);
        list_del(&pclient->head);
    }
    unlock(&pnetwork->lock);
    return pclient;
}

static int32_t network_register_notify(struct network* pnetwork, int32_t event, network_event_notify notify, void* object)
{
    struct network_event_action* paction;
    if(!notify || (event <= NETWORK_EVENT_NONE) || (event >= NETWORK_EVENT_MAX)){
        return -EINVAL;
    }
    paction = (struct network_event_action*)malloc(sizeof(struct network_event_action));
    if(!paction){
        DBG(DBG_ERR,"malloc error\n");
        return -ENOMEM;
    }
    paction->notify = notify;
    paction->object = object;
    lock(&(pnetwork->notify_lock));
    paction->next = pnetwork->paction[event];
    pnetwork->paction[event] = paction;
    unlock(&(pnetwork->notify_lock));
    return 0;
}

static int32_t network_unregister_notify(struct network* pnetwork, int32_t event, void* object)
{
    struct network_event_action *paction,* ptmp;
    if((event <= NETWORK_EVENT_NONE) || (event >= NETWORK_EVENT_MAX)){
        return -EINVAL;
    }
    lock(&(pnetwork->notify_lock));
    paction = pnetwork->paction[event];
    if(paction){
        if(paction->object == object){
            pnetwork->paction[event] = paction->next;
            free(paction);
        }else{
            while(paction->next){
                if(paction->next->object == object){
                    ptmp = paction->next;
                    paction->next = ptmp->next;
                    free(ptmp);
                    break;
                }
                paction = paction->next;
            }
        }
    }
    unlock(&(pnetwork->notify_lock));

    return 0;
}

static int32_t network_trigger_notify(struct network* pnetwork, int32_t event, void* context)
{
    struct network_event_action* paction;
    if((event <= NETWORK_EVENT_NONE) || (event >= NETWORK_EVENT_MAX)){
        return -EINVAL;
    }
    lock(&(pnetwork->notify_lock));
    paction = pnetwork->paction[event];
    while(paction){
        paction->notify(pnetwork, event, paction->object, context);
        paction = paction->next;
    }
    unlock(&(pnetwork->notify_lock));

    return 0;
}

static struct network_operation network_op =
{
    .init = network_init,
    .release = network_release,

    .udp_setup = network_udp_setup,
    .tcp_setup = network_tcp_setup,
    .close = network_close,

    .write = network_write,

    .put_client = network_put_client,
    .get_client = network_get_client,

    .register_notify = network_register_notify,
    .unregister_notify = network_unregister_notify,
    .trigger_notify = network_trigger_notify,
};

int32_t create_init_network(struct network** pnetwork)
{
    int32_t ret;
    struct network* ptmp;
    (*pnetwork) = (struct network*)malloc(sizeof(struct network));
    if(!(*pnetwork)){
        DBG(DBG_ERR,"malloc error\n");
        return -ENOMEM;
    }
    ptmp = *pnetwork;
    memset(ptmp, 0, sizeof(struct network));
    ptmp->op = &network_op;
    ret = ptmp->op->init(ptmp);
    if(ret < 0){
        DBG(DBG_ERR,"init error\n");
        release_destroy_network(ptmp);
        return ret;
    }
    return 0;
}

void release_destroy_network(struct network* pnetwork)
{
    if(pnetwork){
        pnetwork->op->release(pnetwork);
        free(pnetwork);
    }
}
        
